﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NeuroProzessorSvc
{
    /// <summary>
    /// Impementiert die Schnittstelle zum Controller
    /// </summary>
    public class _cls_Controller: MarshalByRefObject, IVisual.I_Controller
    {
        private static System.Threading.Thread _Thread;
        public static _cls_NeuroProzessor _Instance;

        /// <summary>
        /// Statet das lernen
        /// </summary>
        public void START_Learning()
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");

            if (_Thread != null && _Thread.ThreadState == System.Threading.ThreadState.Running)
                return;

            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Thread läuft nicht, deshalb ist das starten zulässig.");
            _Thread = new System.Threading.Thread(_Instance._ExcecLearning);
            _Thread.Start();

            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Warte auf Threadstart");
            while (!_Thread.IsAlive) ;
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Thread gestartet!");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");
        }

        /// <summary>
        /// bricht das lernen ab
        /// </summary>
        public void STOP_Learning()
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");

            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.INFO, "Breche lernen ab...");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Setze Runflag auf Forcestop");
            _Instance._LEARNSTATE = _cls_NeuroProzessor.RUNFLAG.FORCESTOP;
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Warte auf Thread join");
            _Thread.Join();
            _Thread = null;
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Thread beendet");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");
        }

        /// <summary>
        /// Ruft den Status des Lern-Threads ab
        /// </summary>
        /// <returns></returns>
        public System.Threading.ThreadState GET_STATE()
        {
            //IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");

            if (_Thread == null)
            {
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Fake Thread stopped");
                return System.Threading.ThreadState.Stopped;
            }

            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Thread-status: " + _Thread.ThreadState);
            //IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");
            return _Thread.ThreadState;
        }

        /// <summary>
        /// Gibt den letzen Fehlerwert zurück
        /// </summary>
        /// <returns></returns>
        public float GET_LAST_ERR()
        {
            //IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");
            //IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");

            return _Instance.LAST_ERROR;
        }

        /// <summary>
        /// Berechnet die Netzausgabe
        /// </summary>
        /// <param name="inp">Eingabe des netzes</param>
        /// <returns></returns>
        public float[] Compute(float[] inp)
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");

            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Prüfe ob aktuell kein Lernvorgang ausgeführt wird.");
            if(GET_STATE() != System.Threading.ThreadState.Stopped)
            {
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.INFO, "Kann nicht durchgeführt werden, da Lernvorgang läuft.");
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");
                return null;
            }
            else
            {
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");
                return _Instance.COMPUTE(inp);
            }
        }

        /// <summary>
        /// Gbt den Fehler einer Brechnung zurück, nicht das Ergebnis
        /// </summary>
        /// <param name="input">Netzeingabe</param>
        /// <param name="output">erwartete netzausgabe</param>
        /// <returns>Fehler</returns>
        public float COMPUTE_and_CALCERR(float[] input, float[] output)
        {
            return _Instance.COMPUTE_and_CALCERR(input, output);
        }

        /// <summary>
        /// Berechnet die Daten für den 3D-Fehler
        /// </summary>
        /// <param name="l_i">Layer</param>
        /// <param name="n_i">Neuron</param>
        /// <param name="g_1">Gewicht 1</param>
        /// <param name="g_2">Gewicht 2</param>
        /// <param name="err_dist">Werte bereich um den mittelwert der Gewichte(+-)</param>
        /// <returns>Chart 3D-Data</returns>
        public Tuple<float[,], double, double, double> Calc_3D(int l_i, int n_i, int g_1, int g_2, decimal err_dist)
        {
            return _Instance.Calc_3D(l_i, n_i, g_1, g_2, (double)err_dist);
        }

        /// <summary>
        /// Gibt den Inhalt der 3D-Fehler LV zuück (Neuronen und Layer)
        /// </summary>
        /// <returns></returns>
        public string[] GET_3D_Dim()
        {
            return _Instance.GET_3D_Dim().ToArray();
        }

        /// <summary>
        /// Häufigkeitsberechnung für de Wiedersprüche
        /// </summary>
        /// <param name="_EinG_M">Eingaebmuster</param>
        /// <returns></returns>
        public string CALC_Haeufigkeit(bool[,] _EinG_M)
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");

            try
            {
                // für die Remote-debugging
                if (System.Diagnostics.Debugger.IsAttached)
                    System.Diagnostics.Debugger.Break();

                int _cfg_I_LEN0 = _cls_Config._cfg._I.GetLength(0);
                int _cfg_O_LEN1 = _cls_Config._cfg._O[0].GetLength(0);
                int _EinG_M_LEN0 = _EinG_M.GetLength(0);
                int _EinG_M_LEN1 = _EinG_M.GetLength(1);

                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "_cfg_I_LEN0 = " + _cfg_I_LEN0);
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "_cfg_O_LEN1 = " + _cfg_O_LEN1);
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "_EinG_M_LEN0 = " + _EinG_M_LEN0);
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "_EinG_M_LEN1 = " + _EinG_M_LEN1);

                int[] _SUM_ANZ_1_pro_AuEih = new int[_EinG_M_LEN0];
                int[,] _ANZ_1_pro_AuEih = new int[_EinG_M_LEN0, _cfg_O_LEN1];
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "_ANZ_1_pro_AuEih[" + _EinG_M_LEN0 + ", " + _cfg_O_LEN1 + "]");
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "I-Len: " + _cls_Config._cfg._I.GetLength(0) + " O-Len: " + _cfg_I_LEN0);

                int[] _Zuordnung_EM_O = new int[_cfg_I_LEN0];

                // Für jedes Lernparadigma
                for (int i = 0; i < _cfg_I_LEN0; i++)
                {
                    // Für jedes Eingabemuster (z.B. 4)
                    for (int j = 0; j < _EinG_M_LEN0; j++)
                    {
                        bool _Err = false;

                        for (int k = 0; k < _EinG_M_LEN1; k++)
                        {
                            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "i=" + i + " j=" + j + " k=" + k);

                            if (_cls_Config._cfg._I[i][k] >= 0.5 != _EinG_M[j, k])
                            {
                                _Err = true;
                                break;
                            }
                        }

                        // j ist der INDEX richtigen Eingabemsuters
                        if (!_Err)
                        {
                            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, j + " ist der gesuchte EM-Index");

                            _SUM_ANZ_1_pro_AuEih[j]++;
                            _Zuordnung_EM_O[i] = j;

                            for (int x = 0; x < _cfg_O_LEN1; x++)
                                _ANZ_1_pro_AuEih[j,x] += _cls_Config._cfg._O[i][x] >= 0.5 ? 1 : 0;

                            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Summe gebildet!");
                            break;
                        }
                    }
                }

                // für die Remote-debugging
                if (System.Diagnostics.Debugger.IsAttached)
                    System.Diagnostics.Debugger.Break();

                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Computen...");
                // Computen und dieses Summieren
                float[,] _ANZ_1_pro_AuEih_cmp = new float[_EinG_M_LEN0, _cfg_O_LEN1];
                //int[] _SUM_ANZ_1_pro_AuEih_cmp = new int[_EinG_M_LEN0];
                for (int x = 0; x < _EinG_M_LEN0; x++)
                {
                    IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "x=" + x);

                    float[] _MLP_IO = new float[_EinG_M_LEN1];
                    for (int y = 0; y < _EinG_M_LEN1; y++)
                        _MLP_IO[y] = _EinG_M[x, y] ? 1 : 0;
                    
                    _MLP_IO = _Instance.COMPUTE(_MLP_IO);

                    for (int y = 0; y < _cfg_O_LEN1; y++)
                    {
                        _ANZ_1_pro_AuEih_cmp[x, y] = _MLP_IO[y];// >= 0.5 ? 1: 0;
                        //_SUM_ANZ_1_pro_AuEih_cmp[x] += _MLP_IO[y] >= 0.5 ? 1 : 0;
                    }
                }
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "Computen... OK");

                string res = "Übersicht:\r\n";

                for (int i = 0; i < _EinG_M_LEN0; i++)
                {
                    res += (i + 1) + ".\t";
                    for (int j = 0; j < _EinG_M_LEN1; j++)
                        res += _EinG_M[i, j] ? "1" : "0";
                    res += "\t" + _SUM_ANZ_1_pro_AuEih[i].ToString() + "\t";

                    string _tmp = "";
                    for (int j = 0; j < _cfg_O_LEN1; j++)
                        _tmp += _ANZ_1_pro_AuEih[i,j].ToString() + ",";

                    res += "\t" + _tmp + "\r\n";
                }
                res += "\r\n\r\n";

                res += "erwartete Ausgabe:\r\n";
                for (int i = 0; i < _EinG_M_LEN0; i++)
                {
                    res += (i + 1) + ".\t";

                    for (int j = 0; j < _cfg_O_LEN1; j++)
                    {
                        IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "E:" + _ANZ_1_pro_AuEih[i, j] + " / " + _SUM_ANZ_1_pro_AuEih[i]);
                        res += Math.Round((float)_ANZ_1_pro_AuEih[i, j] / (float)_SUM_ANZ_1_pro_AuEih[i], 2) + "\t";
                    }

                    res +="\r\n";
                }
                res += "\r\n";

                res += "reale Ausgabe:\r\n";
                for (int i = 0; i < _EinG_M_LEN0; i++)
                {
                    res += (i + 1) + ".\t";

                    for (int j = 0; j < _cfg_O_LEN1; j++)
                    {
                        IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "R:" + _ANZ_1_pro_AuEih_cmp[i, j] + " / " + _SUM_ANZ_1_pro_AuEih[i]);
                        //res += Math.Round((float)_ANZ_1_pro_AuEih_cmp[i, j] / (float)_SUM_ANZ_1_pro_AuEih[i], 2) + "\t";
                        res += Math.Round(_ANZ_1_pro_AuEih_cmp[i, j], 2) + "\t";
                    }

                    res += "\r\n";
                }
                res += "\r\n";

                res += "Minimaler Fehler:\r\n";

                // Fehler_Calc
                float _MIN_ERR = 0;
                for (int i = 0; i < _cfg_I_LEN0; i++) // _cfg_I_LEN0 == _cfg_O_LEN0
                {
                    for (int j = 0; j < _cfg_O_LEN1; j++)
                    {
                        float _Ausgabe = _ANZ_1_pro_AuEih_cmp[_Zuordnung_EM_O[i], j];
                        _Ausgabe = _cls_Config._cfg._O[i][j] - _Ausgabe;
                        IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.DEBUG, "EP=" + i + "; EM=" + _Zuordnung_EM_O[i] + "; _ANZ_1_pro_AuEih_cmp[_Zuordnung_EM_O[i], j]=" + _ANZ_1_pro_AuEih_cmp[_Zuordnung_EM_O[i], j] + "; Differenz=" + _Ausgabe);
                        _MIN_ERR += _Ausgabe * _Ausgabe;
                    }
                }
                res += Math.Round(_MIN_ERR, 6);
                res += "\r\nLetzer Fehler:\r\n";
                res += Math.Round(_Instance.LAST_ERROR, 6);

                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");
                return res;
            }
            catch (Exception ex) 
            {
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.ERROR, ex.Message);
                IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");
                return ex.Message;
            }
        }

        /// <summary>
        /// Gibt alle Gewichte un deren Postion im Netz zurück
        /// </summary>
        /// <returns></returns>
        public List<Tuple<string, double>> GET_WEIGHTS()
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");

            return _Instance.GET_WEIGHTS();
        }

        /// <summary>
        /// Setzt die Gewichte neu. MANUELLE NUTZUNG
        /// </summary>
        /// <param name="_W"></param>
        public void SET_WEIGHTS(List<Tuple<string, double>> _W)
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");

            _Instance.SET_WEIGHTS(_W);
        }

        /// <summary>
        /// Gibt die Größe der Eingabe oder Ausgabepattern zurück
        /// </summary>
        /// <param name="I_O"></param>
        /// <returns></returns>
        public int GET_DIMENSION(char I_O)
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");

            return _Instance.COUNT(I_O);
        }

        /// <summary>
        /// Speichert das Netz in Dateien
        /// </summary>
        /// <param name="PATH">Speicherpfad</param>
        /// <param name="CFG">Mit Config?</param>
        /// <returns>OK?</returns>
        public bool SAVE_NET(string PATH, bool CFG)
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");

            return _Instance.SAVE_NET(PATH, CFG);
        }

        /// <summary>
        /// Lädt das Netz aus Dateien
        /// </summary>
        /// <param name="PATH">Pfad der Dateien</param>
        /// <returns>OK?</returns>
        public bool LOAD_NET(string PATH)
        {
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "start");
            IVisual._SC.cls_LOG.WRITE_LOG_LINE(IVisual._SC.cls_LOG.LOGLEVEL.METHOD_INVOKE, "ende");

            return _Instance.LOAD_NET(PATH);
        }

        /// <summary>
        /// In welchem Modus befindet sich das Netz (Logic, OCR, usw..)
        /// </summary>
        /// <returns></returns>
        public string GET_Mode()
        {
            return _cls_Config._cfg.mode;
        }

        /// <summary>
        /// Ruft alle Rezeptoren aus dem Config-Object Cache ab
        /// </summary>
        /// <returns></returns>
        public IVisual.Tools.OCR.Receptors OCR_GET_Receptors()
        {
            return _cls_Config._cfg.Object_Cache.OCR_Receptors;
        }
    }
}
